﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using Rodunu.Business.Models.Movies;
using Rodunu.Business.Models.ServiceHelpers;
using Rodunu.Services.Interfaces;
using Rodunu.Services.NetFlix.Json;
using Stratman.Utilities;

namespace Rodunu.Services.NetFlix
{
    public class NetFlixService : INetFlixService, ISimpleIDSearch
    {
        #region Private Members

        private const Int16 MAX_TO_SEARCH = 25;

        /// <summary>
        /// List of cookies that we pass to NetFlix during each HTTP call in order to identify the
        /// current user.
        /// </summary>
        protected CookieCollection cookies = null;

        /// <summary>
        /// Identification string for the current user to use when accessing their RSS feeds.
        /// </summary>
        protected string rssID = "";

        #endregion

        #region Constructors

        /// <summary>
        /// Logs in the user using the provided credentials, getting the list of cookies to use as
        /// well as the user's RSS ID.
        /// </summary>
        /// <param name="email">Email that was used to register the user with NetFlix.</param>
        /// <param name="password">User's NetFlix password.</param>
        public NetFlixService(string email, string password)
        {
            Dictionary<string, string> postFields = new Dictionary<string, string>();

            // Prepare the POST data that we will use for the login attempt
            postFields.Add("email", email);
            postFields.Add("password1", password);
            postFields.Add("movieid", "");
            postFields.Add("trkid", "");
            postFields.Add("SubmitButton", "Click Here to Continue");
            postFields.Add("RememberMe", "True");
            postFields.Add("nextpage", "http://www.netflix.com/");

            // We do a GET request against the login page first, to get an initial collection of
            // cookies
            HttpWebResponse webResponse = WebRequestUtilities.MakeHttpGetRequest("http://www.netflix.com/Login");

            cookies = webResponse.Cookies;
            webResponse.Close();

            // Next, we do the actual login POST request
            webResponse = WebRequestUtilities.MakeHttpPostRequest("https://www.netflix.com/Login", postFields, cookies);

            cookies.Add(webResponse.Cookies);
            webResponse.Close();

            // Do a request against the member homepage to make sure that the user was logged in
            // successfully
            string responseText = WebRequestUtilities.MakeHttpGetRequestTextOnly("http://www.netflix.com/MemberHome", cookies);

            if (!responseText.Contains("<a href=\"http://www.netflix.com/YourAccount\">Your Account</a>"))
                throw new LoginFailedException(email);

            // Get the user's RSS ID
            responseText = WebRequestUtilities.MakeHttpGetRequestTextOnly("http://www.netflix.com/RSSFeeds", cookies);
            rssID = Regex.Match(responseText, "\"http://rss\\.netflix\\.com/QueueRSS\\?id=(?<rssID>.+?)\"").Groups["rssID"].Value;
        }

        #endregion

        #region INetFlixeService Members

        /// <summary>
        /// Searches NetFlix's database for movies that match a set of search terms.
        /// </summary>
        /// <param name="keyword">Search terms that we are to use when querying NetFlix's database.</param>
        /// <returns>A list of movies (if any) that match the search terms.</returns>
        public IList<Title> Search(string keyword)
        {
            // Make the HTTP GET request to NetFlix's search page to get the initial set of results
            string responseText = WebRequestUtilities.MakeHttpGetRequestTextOnly("http://www.netflix.com/Search?v1=" + HttpUtility.UrlEncode(keyword) + "&type=title&row=0&dtl=1");
            //int totalMatches = Convert.ToInt32(Regex.Match(responseText, "Movie Matches</nobr> \\((\\d+)\\)").Groups[1].Value);
            string[] values = Regex.Match(responseText, @"About[\s-0-9]*results").ToString().Split(' ');

            if (values.Length <= 1)
            {
                return new List<Title>();
            }

            int totalMatches = Convert.ToInt32(values[1]);
            List<Title> Movies = new List<Title>();

            if (totalMatches > MAX_TO_SEARCH)
            {
                totalMatches = MAX_TO_SEARCH;
            }

            // If there are more than 50 results, they will be separated into multiple pages, so
            // we have to make a separate HTTP request for each page
            for (int currentIndex = 0; currentIndex < totalMatches; currentIndex++)  //changed from currentIndex += 50
            {
                if (currentIndex != 0)
                    responseText = WebRequestUtilities.MakeHttpGetRequestTextOnly("http://www.netflix.com/Search?v1=" + HttpUtility.UrlEncode(keyword) + "&type=title&row=" + currentIndex.ToString() + "&dtl=1");

                // Extract each movie's information from the results page and add it to the return list
                foreach (Match movie in Regex.Matches(responseText, "<a href=\"http://www.netflix.com/Movie/(.+?)/(?<movieID>\\d+)?(.+?)\">(?<title>[^<(]{1}.+?)</a>"))
                {
                    Match movie1 = movie;
                    if ((Movies.Where(title => title.NetFlixId == Convert.ToInt64(movie1.Groups["movieID"].Value))).SingleOrDefault() != null)
                        continue;

                    string id = movie.Groups["movieID"].Value;
                    JsonMovieDetails jsonDetails = (JsonMovieDetails)WebRequestUtilities.MakeJsonGetRequest("http://www.netflix.com/JSON/BobData?linkCtr=BOX&movieid=" + id, typeof(JsonMovieDetails));
                    Movies.Add(NewTitle(jsonDetails));
                }
            }

            return Movies;
        }

        #region "RSS Related Functions"
        /// <summary>
        /// Gets a list of movies that are currently in the user's queue.
        /// </summary>
        /// <returns>The list of movies currently in the user's queue.</returns>
        public IList<Title> GetQueue()
        {
            return GetMoviesFromRss("http://rss.netflix.com/QueueRSS?id=" + rssID);
        }

        /// <summary>
        /// Gets a list of movies that the user currently has at home.
        /// </summary>
        /// <returns>The list of movies that the user currently has at home.</returns>
        public IList<Title> GetMoviesAtHome()
        {
            return GetMoviesFromRss("http://rss.netflix.com/AtHomeRSS?id=" + rssID);
        }

        /// <summary>
        /// Gets a list of movies that have been recommended to the user by NetFlix.
        /// </summary>
        /// <returns>The list of movies that have been recommended to the user by NetFlix.</returns>
        public IList<Title> GetRecommendations()
        {
            return GetMoviesFromRss("http://rss.netflix.com/RecommendationsRSS?id=" + rssID);
        }

        /// <summary>
        /// Gets a list of movies that have recently been shipped to the user.
        /// </summary>
        /// <returns>The list of movies that have recently been shipped to the user.</returns>
        public IList<Title> GetRecentlyShippedMovies()
        {
            return GetMoviesFromRss("http://rss.netflix.com/TrackingRSS?id=" + rssID, "^Shipped: (?<title>.+)");
        }

        /// <summary>
        /// Gets a list of movies that the user has recently shipped back to NetFlix.
        /// </summary>
        /// <returns>The list of movies that the user has recently shipped back to NetFlix.</returns>
        public IList<Title> GetRecentlyReceivedMovies()
        {
            return GetMoviesFromRss("http://rss.netflix.com/TrackingRSS?id=" + rssID, "^Received: (?<title>.+)");
        }

        /// <summary>
        /// Gets a list of movies that have been newly released.
        /// </summary>
        /// <returns>A list of movies that have been newly released.</returns>
        public IList<Title> GetNewReleases()
        {
            return GetMoviesFromRss("http://rss.netflix.com/NewReleasesRSS");
        }

        /// <summary>
        /// Gets a list of the top 100 most-rented movies.
        /// </summary>
        /// <returns>A list of the top 100 most-rented movies.</returns>
        public IList<Title> GetTop100()
        {
            return GetMoviesFromRss("http://rss.netflix.com/Top100RSS");
        }
        #endregion

        /// <summary>
        /// Gets the information for a particular movie ID.
        /// </summary>
        /// <param name="movieID">Movie ID whose information we are to retrieve.</param>
        /// <returns>A Movie object for the given movie ID.</returns>
        public Title GetMovie(long movieID)
        {
            JsonMovieDetails jsonDetails = (JsonMovieDetails)WebRequestUtilities.MakeJsonGetRequest("http://www.netflix.com/JSON/BobData?linkCtr=BOX&movieid=" + movieID.ToString(), typeof(JsonMovieDetails), cookies);
            return NewTitle(jsonDetails);
        }

        /// <summary>
        /// Searches NetFlix's database of "Watch Now" movies for those matching a set of search
        /// terms.
        /// </summary>
        /// <param name="searchTerms">Search terms that we are to use when querying NetFlix's database.</param>
        /// <returns>A list of watchable movies (if any) that match the search terms.</returns>
        public IList<Title> SearchWatchableMovies(string searchTerms)
        {
            // Make the HTTP GET request to NetFlix's search page
            string responseText = WebRequestUtilities.MakeHttpGetRequestTextOnly("http://www.netflix.com/WiSearch?v1=" + HttpUtility.UrlEncode(searchTerms), cookies);
            string[] values = Regex.Match(responseText, @"About[\s-0-9]*results").ToString().Split(' ');

            if (values.Length <= 1)
            {
                return new List<Title>();
            }

            IList<Title> Movies = new List<Title>();

            // Extract each movie's information from the results page and add it to the return list
            foreach (Match movie in Regex.Matches(responseText, "<a href=\"http://www.netflix.com/Movie/(.+?)/(?<movieID>\\d+)?(.+?)\">(?<title>[^<(]{1}.+?)</a>"))
            {
                Match movie1 = movie;
                if ((Movies.Where(title => title.NetFlixId == Convert.ToInt64(movie1.Groups["movieID"].Value))).SingleOrDefault() != null)
                    continue;

                string id = movie.Groups["movieID"].Value;
                JsonMovieDetails jsonDetails = (JsonMovieDetails)WebRequestUtilities.MakeJsonGetRequest("http://www.netflix.com/JSON/BobData?linkCtr=BOX&movieid=" + id, typeof(JsonMovieDetails));
                Movies.Add(NewTitle(jsonDetails));//movies.Add(Convert.ToInt64(movie.Groups["movieID"].Value), new Movie(Convert.ToInt64(movie.Groups["movieID"].Value), movie.Groups["title"].Value));
            }

            return Movies;
        }

        /// <summary>
        /// Adds the specified movie ID to the user's queue.
        /// </summary>
        /// <param name="movieID">ID of the movie that we are to add to the queue.</param>
        public QueueResult AddToQueue(long movieID)
        {
            string url = "http://www.netflix.com/AddToQueue?movieid=" + movieID;

            HttpWebResponse webResponse = WebRequestUtilities.MakeHttpGetRequest(url, cookies);
            string response = WebRequestUtilities.MakeHttpGetRequestTextOnly(webResponse.ResponseUri.AbsoluteUri, cookies);

            if (response.ToUpper().Contains("HAS BEEN ADDED TO YOUR"))
            {
                return QueueResult.Success;
            }

            return response.ToUpper().Contains("THIS MOVIE IS ALREADY IN YOUR") ? QueueResult.AlreadyInQueue : QueueResult.Failed;
        }

        /// <summary>
        /// Removes a specified movie from the user's queue.
        /// </summary>
        /// <param name="movieID">ID of the movie that we are to remove.</param>
        public void DeleteFromQueue(long movieID)
        {
            // Make the HTTP POST call to remove the movie from the queue
            Dictionary<string, string> postFields = new Dictionary<string, string>();
            postFields.Add("updateQueueBtn", "updateQueueBtn");
            postFields.Add("R" + movieID.ToString(), "on");

            string responseText = WebRequestUtilities.MakeHttpPostRequestTextOnly("http://www.netflix.com/Queue", postFields, cookies);
        }

        /// <summary>
        /// Helper method that extracts movie information from RSS feeds.
        /// </summary>
        /// <param name="rssURL">URL from which we are to retrieve the feed data.</param>
        /// <returns>A list of movies that were in the RSS data.</returns>
        protected static IList<Title> GetMoviesFromRss(string rssURL)
        {
            return GetMoviesFromRss(rssURL, "(\\d+\\- )?(?<title>.+)");
        }

        /// <summary>
        /// Helper method that extracts movie information from RSS feeds, allowing the inclusion
        /// of a regular expression to be applied against the title to determine which movies are 
        /// to be brought back.
        /// </summary>
        /// <param name="rssURL">URL from which we are to retrieve the feed data.</param>
        /// <param name="matchRegex">Regular expression to be applied against the title.</param>
        /// <returns>A list of matching movies that were in the RSS data.</returns>
        protected static IList<Title> GetMoviesFromRss(string rssURL, string matchRegex)
        {
            // Get the RSS XML from the URL
            string responseText = WebRequestUtilities.MakeHttpGetRequestTextOnly(rssURL);
            XmlDocument rssXML = new XmlDocument();
            IList<Title> Movies = new List<Title>();

            rssXML.LoadXml(responseText);

            XmlNodeList queueItems = rssXML.SelectNodes("/rss/channel/item");

            if (queueItems != null)
            {
                foreach (XmlNode queueItem in queueItems)
                {
                    string title = Regex.Match(queueItem.SelectSingleNode("title").InnerText, matchRegex).Groups["title"].Value;

                    // If the movie's title doesn't match the regular expression, don't add it to the
                    // return list
                    if (String.IsNullOrEmpty(title))
                        continue;

                    // Otherwise, create the movie object and add it to the list
                    long id = Convert.ToInt64(Regex.Match(queueItem.SelectSingleNode("link").InnerText, "http://www.netflix.com/Movie/.+?/(?<id>\\d+)").Groups["id"].Value);
                    JsonMovieDetails jsonDetails = (JsonMovieDetails)WebRequestUtilities.MakeJsonGetRequest("http://www.netflix.com/JSON/BobData?linkCtr=BOX&movieid=" + id, typeof(JsonMovieDetails));
                    Movies.Add(NewTitle(jsonDetails));
                }
            }

            return Movies;
        }

        /// <summary>
        /// Explicitly logs the current session out from NetFlix's site.
        /// </summary>
        public void Dispose()
        {
            WebRequestUtilities.MakeHttpGetRequestTextOnly("http://www.netflix.com/LogoutPage", cookies);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Creates a new Movie Title object.
        /// </summary>
        /// <param name="jsonDetails">Object containing movie details.</param>
        /// <returns>A new Title</returns>
        private static Title NewTitle(JsonMovieDetails jsonDetails)
        {
            Title movie = new Title();

            // Instantiate the various member variables
            movie.Name = jsonDetails.title;
            movie.NetFlixId = Convert.ToInt64(jsonDetails.id);
            movie.MPAARating = jsonDetails.mpaa ?? string.Empty;
            movie.Year = Convert.ToInt32(jsonDetails.year);
            movie.Plot = jsonDetails.synopsis ?? string.Empty;
            movie.Genres = new List<string> { jsonDetails.genre };
            if (!string.IsNullOrEmpty(jsonDetails.starClarityData.average))
            {

                movie.Rating = Convert.ToInt32(Convert.ToDecimal(jsonDetails.starClarityData.average));
            }

            // Populate the list of actors and directors
            if (jsonDetails.directors != null)
            {
                if (jsonDetails.directors.Length > 0)
                {
                    for (int i = 0; i < jsonDetails.directors.Length; i++)
                        movie.Directors.Add(new Director(jsonDetails.directors[i].name));
                }
            }

            if (jsonDetails.starring != null)
            {
                if (jsonDetails.starring.Length > 0)
                {
                    for (int i = 0; i < jsonDetails.starring.Length; i++)
                        movie.Cast.Add(new Role(string.Empty, jsonDetails.starring[i].name));
                }
            }

            return movie;
        }

        #endregion

        #region Not Implemented
        /// <summary>
        /// Movies the specified movie ID to a different location in the user's queue.
        /// </summary>
        /// <param name="movieID">ID of the movie that we are to move.</param>
        /// <param name="newPosition">New position in the queue that the movie is to occupy.</param>
        //public void ReorderQueue(long movieID, int newPosition)
        //{
        //    // Make the JSON call to reorder the user's queue
        //    JsonQueueReorderResponse response = (JsonQueueReorderResponse)WebRequestUtilities.MakeJsonGetRequest("http://www.netflix.com/QueueReorder?movieid=" + movieID.ToString() + "&pos=" + newPosition.ToString(), typeof(JsonQueueReorderResponse), cookies);

        //    // Check to make sure that the operation was successful
        //    if (!response.result)
        //        throw new QueueException("Unable to reorder the queue.");
        //}
        #endregion

        #region ISimpleIDSearch Members

        /// <summary>
        /// Searches NetFlix's database for movies that match a set of search terms.
        /// </summary>
        /// <param name="keyword">Search terms that we are to use when querying NetFlix's database.</param>
        /// <returns>A list of movies (if any) that match the search terms.</returns>
        public IList<SimpleID> GetQuickResults(string keyword)
        {

            // Make the HTTP GET request to NetFlix's search page to get the initial set of results
            string responseText = WebRequestUtilities.MakeHttpGetRequestTextOnly("http://www.netflix.com/Search?v1=" + HttpUtility.UrlEncode(keyword) + "&type=title&row=0&dtl=1");
            //int totalMatches = Convert.ToInt32(Regex.Match(responseText, "Movie Matches</nobr> \\((\\d+)\\)").Groups[1].Value);
            string[] values = Regex.Match(responseText, @"About[\s-0-9]*results").ToString().Split(' ');

            if (values.Length <= 1)
            {
                return new List<SimpleID>();
            }

            int totalMatches = Convert.ToInt32(values[1]);
            List<SimpleID> Movies = new List<SimpleID>();

            if (totalMatches > MAX_TO_SEARCH)
            {
                totalMatches = MAX_TO_SEARCH;
            }

            // If there are more than 50 results, they will be separated into multiple pages, so
            // we have to make a separate HTTP request for each page
            for (int currentIndex = 0; currentIndex < totalMatches; currentIndex++)  //changed from currentIndex += 50
            {
                if (currentIndex != 0)
                    responseText = WebRequestUtilities.MakeHttpGetRequestTextOnly("http://www.netflix.com/Search?v1=" + HttpUtility.UrlEncode(keyword) + "&type=title&row=" + currentIndex.ToString() + "&dtl=1");

                // Extract each movie's information from the results page and add it to the return list
                foreach (Match movie in Regex.Matches(responseText, "<a href=\"http://www.netflix.com/Movie/(.+?)/(?<movieID>\\d+)?(.+?)\">(?<title>[^<(]{1}.+?)</a>"))
                {
                    Match movie1 = movie;
                    if ((Movies.Where(title => title.Id == movie1.Groups["movieID"].Value)).SingleOrDefault() != null)
                        continue;

                    SimpleID simpleID = new SimpleID
                    {
                        Id = movie.Groups["movieID"].Value ?? string.Empty,
                        Name = movie.Groups["title"].Value ?? string.Empty
                    };
                    Movies.Add(simpleID);
                }
            }

            return Movies;
        }

        #endregion
    }
}
